﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ProgramMain.Framework
{
    public static class EventPriorityTypeConverter
    {
        public static Array _EventPriorityEnum = null;
        public static Array EventPriorityEnum
        {
            get
            {
                if (_EventPriorityEnum == null)
                    _EventPriorityEnum = Enum.GetValues(typeof(WorkerMessageThread.EventPriorityType));
                return _EventPriorityEnum;
            }
        }
        
        public static int Length
        {
            get
            {
                return EventPriorityEnum.Length;
            }
        }

        public static WorkerMessageThread.EventPriorityType ToEventPriorityType(this Int32 i)
        {
            if (i >= 0 && i < EventPriorityEnum.Length)
                return (WorkerMessageThread.EventPriorityType)Convert.ChangeType(EventPriorityEnum.GetValue(i), typeof(WorkerMessageThread.EventPriorityType));
            else
                return WorkerMessageThread.EventPriorityType.Idle;
        }
    }

    public class WorkerMessageThread
    {
        private AutoResetEvent autoEvent = new AutoResetEvent(false);

        private Control DelegateControl;

        public WorkerMessageThread(Control DelegateCOntrol)
        {
            //для делегейта в родительский поток
            this.DelegateControl = DelegateCOntrol;

            CreateDownloadThread();
        }

        ~WorkerMessageThread()
        {
            Terminating = true;
        }

        private ManualResetEvent terminateEvent = new ManualResetEvent(false);
        private bool _Terminating = false;
        public bool Terminating
        {
            set
            {
                if (_Terminating != value && value)
                {
                    _Terminating = true;
                    Terminate();
                }
            }
            get
            {
                return _Terminating;
            }
        }

        protected enum WorkerEventType { None, RedrawLayer, DownloadImage, DrawImage, ReloadData, AddDBObject };
        
        public enum EventPriorityType { Idle = 0, Low = 1, BelowNormal = 2, Normal = 3, AboveNormal = 4, High = 5, Critical = 6 };

        protected class WorkerEvent : IComparable
        {
            // Summary:
            //     Represents an event with no event data.
            public static readonly WorkerEvent Empty = new WorkerEvent();

            private WorkerEventType _EventType = WorkerEventType.None;
            public WorkerEventType EventType
            {
                get
                {
                    return _EventType;
                }
            }

            private EventPriorityType _EventPriority = EventPriorityType.Normal;
            public EventPriorityType EventPriority
            {
                get
                {
                    return _EventPriority;
                }
            }

            private bool _Collapsible;
            public bool IsCollapsible
            {
                get
                {
                    return _Collapsible;
                }
            }

            private WorkerEvent()
            {
                _Collapsible = false;
            }

            public WorkerEvent(WorkerEventType pEventType)
            {
                _EventType = pEventType;
                _Collapsible = false;
                _EventPriority = EventPriorityType.Normal;
            }

            public WorkerEvent(WorkerEventType pEventType, bool pIsCollapsible)
            {
                _EventType = pEventType;
                _Collapsible = pIsCollapsible;
                _EventPriority = EventPriorityType.Normal;
            }

            public WorkerEvent(WorkerEventType pEventType, bool pIsCollapsible, EventPriorityType pPriorityType)
            {
                _EventType = pEventType;
                _Collapsible = pIsCollapsible;
                _EventPriority = pPriorityType;
            }

            virtual public int CompareTo(object obj)
            {
                if (obj is WorkerEvent)
                {
                    if (((WorkerEvent)obj)._EventType > this.EventType)
                        return 1;
                    else if (((WorkerEvent)obj)._EventType < this.EventType)
                        return -1;
                    else
                        return 0;
                }
                return -1;
            }
        }

        private static readonly int WorkerEventSize = 1000;
        private List<WorkerEvent> WorkerEventList = new List<WorkerEvent>();
        private readonly SemaphoreSlim lockWE = new SemaphoreSlim(1, 1);

        public class OwnerEventArgs
        {
            // Summary:
            //     Represents an event with no event data.
            public static readonly OwnerEventArgs Empty = new OwnerEventArgs();

            public OwnerEventArgs()
            {
                
            }
        }

        public delegate void OwnerEventHandler<T>(object sender, T e) where T : OwnerEventArgs;

        protected delegate void OwnerEventDelegate<T>(T EventParams) where T : OwnerEventArgs;

        protected void FireOwnerEvent<T>(OwnerEventDelegate<T> OwnerEvent, T EventParams) where T : OwnerEventArgs
        {
            if (Terminating) return;
            //синхронный вызов из рабочего потока в поток приложения
            DelegateControl.Invoke(OwnerEvent, new Object[] { EventParams });
        }

        private void Terminate()
        {
            while (!terminateEvent.WaitOne(100))
            {
                WakeupWorkerThread();
                Application.DoEvents();
            }
        }
        
        virtual protected void OnControlClosing()
        {
        }

        protected virtual bool DispatchThreadEvents(WorkerEvent WorkerEvent)
        {
            return false;
        }

        protected void PutWorkerThreadEvent(WorkerEvent WorkerEvent)
        {
            try
            {
                lockWE.Wait();
                
                if (!WorkerEvent.IsCollapsible
                    || WorkerEventList.Find(we => we.CompareTo(WorkerEvent) == 0) == null)
                {
                    if (WorkerEventList.Count > WorkerEventSize)
                    {
                        WorkerEventList.RemoveAt(0);
                    }
                    WorkerEventList.Add(WorkerEvent);
                }
            }
            finally
            {
                lockWE.Release();
            }
            WakeupWorkerThread();
        }

        protected void PutWorkerThreadEvent(WorkerEventType WorkerEventType, bool IsCollapsible, EventPriorityType PriorityType)
        {
            PutWorkerThreadEvent(new WorkerEvent(WorkerEventType, IsCollapsible, PriorityType));
        }

        protected void PutWorkerThreadEvent(WorkerEventType WorkerEventType)
        {
            PutWorkerThreadEvent(new WorkerEvent(WorkerEventType, false, EventPriorityType.Normal));
        }

        protected WorkerEvent PopupWorkerThreadEvent()
        {
            return PopupWorkerThreadEvent(WorkerEventType.None);
        }

        protected void DropWorkerThreadEvents(WorkerEventType WorkerEventType)
        {
            try
            {
                lockWE.Wait();

                if (WorkerEventType != WorkerEventType.None)
                {
                    WorkerEventList.RemoveAll(we => we.EventType == WorkerEventType);
                }
            }
            finally
            {
                lockWE.Release();
            }
        }

        private void CreateDownloadThread()
        {
            ThreadStart threadDeligate = new ThreadStart(WorkerThread);
            Thread thread = new Thread(threadDeligate);

            thread.Priority = ThreadPriority.BelowNormal;
            thread.Start();
        }

        private void WorkerThread()
        {
            do
            {
                autoEvent.WaitOne();
                if (Terminating)
                    break;
                DoThreadWork();
            } while (true);
            
            OnControlClosing();
            
            terminateEvent.Set();
        }

        private void DoThreadWork()
        {
            WorkerEvent WorkerEvent;
            do
            {
                WorkerEvent = PopupWorkerThreadEvent();
                try
                {
                    if (WorkerEvent != null)
                    {
                        DispatchThreadEvents(WorkerEvent);
                    }
                }
                catch (Exception ex)
                {
                    //do nothing
                    System.Diagnostics.Trace.WriteLine(ex.Message);
                }
            } while (WorkerEvent != WorkerEvent.Empty && !Terminating);
        }

        private void WakeupWorkerThread()
        {
            autoEvent.Set();
        }

        private WorkerEvent PopupWorkerThreadEvent(WorkerEventType WorkerEventType)
        {
            //выбираем задания из очереди, RedrawLayer имеет низший приоритет
            WorkerEvent res = WorkerEvent.Empty;
            try
            {
                lockWE.Wait();

                //если не задан тип задание, то ищем любой в соответствии с приоритетом)
                for (int i = EventPriorityTypeConverter.Length - 1; i >= 0; i--)
                {
                    EventPriorityType item = i.ToEventPriorityType();

                    WorkerEvent tmp = WorkerEventList.Find(we =>
                        we.EventPriority == item
                        && (WorkerEventType == WorkerEventType.None || WorkerEventType == we.EventType));
                    if (tmp != null)
                    {
                        res = tmp;
                        break;
                    }
                }

                if (res != WorkerEvent.Empty)
                {
                    //выбираем все/или одно задание данного типа(зависит от типа задания)
                    if (res.IsCollapsible)
                    {
                        WorkerEventList.RemoveAll(we => we.CompareTo(res) == 0);
                    }
                    else
                    {
                        WorkerEventList.Remove(res);
                    }
                }
            }
            finally
            {
                lockWE.Release();
            }
            return res;
        }
    }
}
